﻿using PinusTools.Consts;
using PinusTools.Enums;
using PinusTools.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PinusTools.DataConverter
{
    public class Message
    {
        public static byte[] Encode(int id, MessageEnum type, int route, byte[] msg)
        {
            var idData = VarintBitConverter.GetVarintBytes(Convert.ToUInt32(id));
            var routeData = new[] { (byte)((route >> 8) & 0xff), (byte)(route & 0xff) };
            return Encode(idData, type, routeData, msg);
        }

        public static byte[] Encode(int id, MessageEnum type, string route, byte[] msg)
        {
            var idData = VarintBitConverter.GetVarintBytes(Convert.ToUInt32(id));

            var routeBytes = Protocol.StrEncode(route);
            var routeList = new List<byte>();
            routeList.Add((byte)routeBytes.Length);
            routeList.AddRange(routeBytes);
            var routeData = routeList.ToArray();

            return Encode(idData, type, routeData, msg);
        }

        public static byte[] Encode(int id, MessageEnum type, int compressRoute, object route, byte[] msg)
            => Encode(id, type, Convert.ToBoolean(compressRoute), route, msg);

        public static byte[] Encode(int id, MessageEnum type, bool compressRoute, object route, byte[] msg)
        {
            if (compressRoute)
            {
                if (route is Int32 val)
                    return Encode(id, type, val, msg);
                else
                    throw new Exception("启用了路由加密，但是Route非Int32类型");
            }
            else
            {
                if (route is String val)
                    return Encode(id, type, val, msg);
                else
                    throw new Exception("启用了路由加密，但是Route非String类型");
            }
        }

        private static byte[] Encode(byte[] id, MessageEnum type, byte[] route, byte[] msg)
        {
            var list = new List<byte>();

            // add flag
            list.Add((byte)(((int)type << 1) | 0));

            // add message id
            if (MsgHasId(type))
                list.AddRange(id);

            // add route
            if (MsgHasRoute(type))
                list.AddRange(route);

            // add body
            if (msg != null && msg.Length > 0)
                list.AddRange(msg);

            return list.ToArray();
        }

        public static MessageDecodeMod Decode(byte[] buffer)
        {
            var list = buffer.ToList();
            var id = 0;

            var flag = (int)list[0];
            list.RemoveAt(0);

            var compressRoute = Convert.ToBoolean(flag & MsgConst.MSG_COMPRESS_ROUTE_MASK);
            var type = (MessageEnum)((flag >> 1) & MsgConst.MSG_TYPE_MASK);

            if (MsgHasId(type))
            {
                var idList = new List<byte>();
                do
                {
                    idList.Add(list[0]);
                    list.RemoveAt(0);
                } while (list[0] >= 128);
                id = (int)VarintBitConverter.ToUInt32(idList.ToArray());
            }

            object route = String.Empty;
            if (MsgHasRoute(type))
            {
                if (!compressRoute)
                {
                    var routeLen = (int)list[0];
                    list.RemoveAt(0);

                    if (routeLen > 0)
                    {
                        var tempRoute = list.Take(routeLen).ToArray();
                        list.RemoveRange(0, routeLen);
                        route = Protocol.StrDecode(tempRoute);
                    }
                }
                else
                {
                    route = ((int)(list[0]) << 8) | (int)list[1];
                    list.RemoveRange(0, 2);
                }
            }

            return new MessageDecodeMod
            {
                Id = (int)id,
                Type = type,
                CompressRoute = compressRoute,
                Route = route,
                Body = list.ToArray(),
                StrBody = Protocol.StrDecode(list.ToArray())
            };
        }


        private static bool MsgHasId(MessageEnum type) =>
            (type == MessageEnum.TYPE_REQUEST) ||
            (type == MessageEnum.TYPE_RESPONSE);

        private static bool MsgHasRoute(MessageEnum type) =>
            (type == MessageEnum.TYPE_REQUEST) ||
            (type == MessageEnum.TYPE_NOTIFY) ||
            (type == MessageEnum.TYPE_PUSH);
    }
}